#include <cppinc/log.h>
#include <cppinc/kzshell.h>

typedef enum {
    LOG_NOINPUT,
    LOG_INFO,
    LOG_USR_TYPING,
    LOG_EVENT,
    LOG_BAR,
    LOG_CRITICAL,
} LOG_TYPE;
static LOG_TYPE lastLogType = LOG_NOINPUT;


enum SHELL_STATE {
    SHELL_STATE_TYPING,
    SHELL_STATE_ESC,
    SHELL_STATE_SQUARE,
    SHELL_STATE_WAVE
};

enum VT100_KEY_DEFINE
{
    BS = 0x08,
    HT = 0x09,
    LF = 0x0A,
    CR = 0x0D,
    ESC = 0x1B,
    DEL = 0x7F,
    CSI = 0x9B,
};

void kzshell::CliProcByte(char c)
{
    keycode_t ret;
    ret = get_keycode(c);
    run_key_func(ret, c);
}

keycode_t kzshell::get_keycode(uint8_t c)
{
    keycode_t ret = UNKNOWN;

    if (m_state == SHELL_STATE_TYPING) {
        bool isPrintAble = (c >= 32) && (c <= 126);
        if (isPrintAble)
            return PRINTABLE_KEY;
        switch (c) {
            case BS: return BACKSPACE;
            case LF:
            case CR: return ENTER;
            case DEL: return BACKSPACE;
            case HT: return TAB;
            case ESC: m_state = SHELL_STATE_ESC; return UNKNOWN;
        }
    }

    switch (m_state) {
        case SHELL_STATE_ESC:
            if (c == '[') {
                m_state = SHELL_STATE_SQUARE;
            } else {
                m_state = SHELL_STATE_TYPING;
            }
            break;
        case SHELL_STATE_SQUARE:
            m_state = SHELL_STATE_TYPING;
            switch (c) {
                case 'A': ret = UP; break;
                case 'B': ret = DOWN; break;
                case 'C': ret = RIGHT; break;
                case 'D': ret = LEFT; break;
                case '3': m_state = SHELL_STATE_WAVE; break;
                default: break;
            }
            break;
        case SHELL_STATE_WAVE:
            m_state = SHELL_STATE_TYPING;
            if (c == '~') {
                ret = DELETE;
            }
            break;
        default: m_state = SHELL_STATE_TYPING; break;
    }

    return ret;
}

void kzshell::run_key_func(keycode_t x, char c)
{
    if (x != UP && x != DOWN && m_state == SHELL_STATE_TYPING) {
        m_his_cmd_request_index = 0;
        m_shadow_line[0] = '\0';
    }

    switch (x)
    {
        case PRINTABLE_KEY: normal_key_func(c); break;
        case ENTER: enter_key_func(c); break;
        case TAB: tab_key_func(c); break;
        case UP: up_key_func(c); break;
        case DOWN: down_key_func(c); break;
        case LEFT: left_key_func(c); break;
        case RIGHT: right_key_func(c); break;
        case BACKSPACE: backspace_key_func(c); break;
        case DELETE: delete_key_func(c); break;
        case UNKNOWN: unknown_key_func(c); break;
    }
}

void kzshell::print_prompt()
{
    LOG("\r\n%s", SHELL_PROMPT);
    m_cursor_pos = 0;
    m_rcv_len = 0;
    m_line[0] = '\0';
}

void kzshell::normal_key_func(uint8_t c)
{
    if (m_rcv_len == SHELL_LEN_MAX - 2) {
        LOG("\r\nline too long!\r\n");
        print_prompt();
    }

    if (m_rcv_len == m_cursor_pos) {
        m_line[m_cursor_pos] = c;
        m_cursor_pos++;
    } else {
        for (uint32_t i = m_rcv_len; i >= m_cursor_pos; i--) {
            m_line[i + 1] = m_line[i];
        }
        m_line[m_cursor_pos] = c;
        m_cursor_pos++;
    }

    m_rcv_len++;
    m_line[m_rcv_len] = '\0';

    if (m_rcv_len != m_cursor_pos) {
        LOG("\x1b[%d@", 1);
    }
    LOG("%c", c);
};

void kzshell::enter_key_func(uint8_t c)
{
    m_line[m_rcv_len] = '\0';
    if (m_rcv_len != 0) {
        run_cmdline();
    } else {
        LOG("\r\n");
    }

    print_prompt();
};

void kzshell::dump_shell()
{
    LOG("\r\ndump shell:\r\n");
    for (int idx = 0; idx < m_cmdMap.nodeMax(); idx++) {
        LOG("%s: 0x%08x\r\n", m_cmdMap[idx]->key.c_str(), m_cmdMap[idx]->value);
    }
}

void kzshell::tab_key_func(uint8_t c)
{
    if (m_rcv_len == 0) {
        LOG("\r\n");
        dump_shell();
        print_prompt();
    } else {
        complete();
    }
}

void kzshell::up_key_func(uint8_t c)
{
    uint8_t his_id = 0;
    int i;


    if (m_his_cmd_count == 0 || m_his_cmd_request_index >= m_his_cmd_count) {
        return;
    }

    if (m_shadow_line[0] == '\0' && m_his_cmd_request_index == 0) {
        strcpy(m_shadow_line, m_line);
    }

    m_his_cmd_request_index++;

    // convert requst index to his_id
    his_id = (m_his_cmd_rear + SHELL_HISTORY_CMD_NUM - m_his_cmd_request_index) % SHELL_HISTORY_CMD_NUM;

    LOG("\x1b[2K\r");
    print_prompt();
    strcpy(m_line, m_his_cmd[his_id]);
    m_rcv_len = strlen(m_line);
    m_cursor_pos = m_rcv_len;
    LOG("%s", m_line);
};

void kzshell::down_key_func(uint8_t c)
{
    char *tmp;
    uint8_t his_id = 0;
    int i;

    if (m_his_cmd_request_index == 0) {
        return;
    }

    m_his_cmd_request_index--;

    if (m_his_cmd_request_index == 0) {
        tmp = m_shadow_line;
    } else {
        // convert requst index to his_id
        his_id = (m_his_cmd_rear + SHELL_HISTORY_CMD_NUM - m_his_cmd_request_index) % SHELL_HISTORY_CMD_NUM;
        tmp = m_his_cmd[his_id];
    }

    LOG("\x1b[2K\r");
    print_prompt();
    strcpy(m_line, tmp);
    m_rcv_len = strlen(m_line);
    m_cursor_pos = m_rcv_len;
    LOG("%s", m_line);
}

void kzshell::left_key_func(uint8_t c)
{
    if (m_cursor_pos == 0)
        return;
    m_cursor_pos--;
    LOG("\x1b[D");
}

void kzshell::right_key_func(uint8_t c)
{
    if (m_cursor_pos == m_rcv_len)
        return;
    m_cursor_pos++;
    LOG("\x1b[C");
}

void kzshell::backspace_key_func(uint8_t c)
{
    int i;

    if (m_cursor_pos == 0)
        return;
    m_cursor_pos--;
    m_rcv_len--;
    for (i = m_cursor_pos; i < m_rcv_len; i++) {
        m_line[i] = m_line[i + 1];
    }
    LOG("\x1b[D\x1b[P");
}


void kzshell::delete_key_func(uint8_t c)
{
    if (m_cursor_pos == m_rcv_len)
        return;
    m_rcv_len--;
    for (uint32_t i = m_cursor_pos; i < m_rcv_len; i++) {
        m_line[i] = m_line[i + 1];
    }
    LOG("\x1b[P");
    return;
}

void kzshell::unknown_key_func(uint8_t c)
{
    return;
}

int split_cmdline(char *line, uint8_t *argc, char *argv[], uint8_t argv_sz);

int kzshell::run_cmdline()
{
    uint8_t argc = 0;
    char *argv[SHELL_MAX_PARAM_NUM];
    char line_tmp[SHELL_LEN_MAX];

    argv[0] = NULL;

    strcpy(line_tmp, m_line);
    int ret = split_cmdline(line_tmp, &argc, argv, SHELL_MAX_PARAM_NUM);

    if (ret < 0) {
        LOG("\r\nInput parameter count > %d!\r\n", SHELL_MAX_PARAM_NUM);
        return ret;
    }

    std::string cmd(m_line);
    auto it = m_cmdMap.find(cmd);
    if (it != nullptr) {
        LOG("\r\n");
        if (it->value != nullptr) {
            ret = it->value(argc, argv);
        } else {
            LOG("cmd %s not implemented.\r\n", cmd.c_str());
        }

    } else if (argv[0]) {
        LOG("\r\ncan't find cmd %s.\r\n", argv[0]);
    } else {
        LOG("\r\n");
    }

    if (!ret) {
        add_history_cmd();
    }

    return ret;
}

void kzshell::RegCmd(std::string cmd, CliTaskCallBack exe)
{
    m_cmdMap.append(cmd, exe);
    LOG("reg: [%s, 0x%08x], %d reged in total\r\n", cmd.c_str(), exe, m_cmdMap.nodeMax());
}

void kzshell::add_history_cmd()
{
    int i;

    // Don't add empty command to history
    if (!m_line[0])
        return;

    // Avoid duplicate storage
    if (m_his_cmd_count > 0) {
        if (!strcmp(m_line, m_his_cmd[m_his_cmd_rear - 1])) {
            return;
        }
    }

    strncpy(m_his_cmd[m_his_cmd_rear], m_line, m_rcv_len);
    m_his_cmd[m_his_cmd_rear][m_rcv_len] = '\0';

    if (m_his_cmd_count < SHELL_HISTORY_CMD_NUM) {
        m_his_cmd_count++;
    }

    m_his_cmd_rear++;
    m_his_cmd_rear %= SHELL_HISTORY_CMD_NUM;
}

int kzshell::complete()
{
    char *cursor = m_line + m_cursor_pos;
    char *word_start = cursor;
    char *word_end = cursor;
    int word_len;
    int match_count = 0;
    const char *match = NULL;
    int i;
    char auto_complete_words[][3] = { "-h", "-p", "-t" };
    const uint16_t auto_complete_words_size = sizeof(auto_complete_words) / sizeof(auto_complete_words[0]);


    m_line[m_rcv_len] = '\0';

    // Find the start and end position of current word
    while (word_start > m_line && *(word_start - 1) != ' ') {
        word_start--;
    }
    while (*word_end != ' ' && *word_end != '\0') {
        word_end++;
    }
    word_len = word_end - word_start;

    // Don't complete if cursor is at whitespace or after a word
    if (word_len == 0 || (*cursor == ' ' && cursor > m_line && *(cursor - 1) == ' ')) {
        return 0;
    }


    // Check if it's the first word (command name)
    if (word_start == m_line) {
        // Search for matching commands in cmd_table
        for (int idx = 0; idx < m_cmdMap.nodeMax(); idx++) {
            const char* pCmd = m_cmdMap[idx]->key.c_str();
            if (strncmp(word_start, pCmd, word_len) == 0) {
                if (match_count == 0) {
                    match = pCmd;
                } else if (strcmp(match, pCmd) != 0) {
                    match = NULL; // Multiple different matches found
                }
                match_count++;
            }
        }
    } else {
        // Search for matching words in auto_complete_words
        for (i = 0; i < auto_complete_words_size; i++) {
            if (strncmp(word_start, auto_complete_words[i], word_len) == 0) {
                if (match_count == 0) {
                    match = auto_complete_words[i];
                } else if (match && strcmp(match, auto_complete_words[i]) != 0) {
                    match = NULL; // Multiple different matches found
                }
                match_count++;
            }
        }
    }


    if (match_count > 1) {
        // Multiple matches found, show all possible options
        LOG("\r\n");
        if (word_start == m_line) {
            for (int idx = 0; idx < m_cmdMap.nodeMax(); idx++) {
                const char* pCmd = m_cmdMap[idx]->key.c_str();
                if (strncmp(word_start, pCmd, word_len) == 0) {
                    LOG("%s ", pCmd);
                }
            }
        } else {
            for (i = 0; i < auto_complete_words_size; i++) {
                if (strncmp(word_start, auto_complete_words[i], word_len) == 0) {
                    LOG("%s ", auto_complete_words[i]);
                }
            }
        }
        LOG("\r\n");
        LOG("%s: ", SHELL_PROMPT);
        LOG("%s", m_line);
        if (m_rcv_len - m_cursor_pos > 0) {
            LOG("\x1b[%dD", m_rcv_len - m_cursor_pos); // Move cursor to correct position
        }
    } else if (match_count == 1 && match != NULL) {
        // Single match found, complete it
        int complete_len = strlen(match) - word_len;
        if (complete_len > 0) {
            if (m_rcv_len + complete_len < SHELL_LEN_MAX) {
                memmove(word_end, word_end + complete_len, strlen(word_end) + 1);
                memcpy(word_start + word_len, match + word_len, complete_len);
                m_rcv_len += complete_len;
                m_cursor_pos += complete_len;
                LOG("%s", match + word_len);
            }
        }
    }

    return 0;
}

int split_cmdline(char *line, uint8_t *argc, char *argv[], uint8_t argv_sz)
{
    char *p = line;
    uint8_t arg_count = 0;
    char *arg_start = NULL;

    while (*p) {
        if (!arg_start && *p == ' ') {
            p++;
            continue;
        }

        if (!arg_start) {
            arg_start = p;
        }

        if (*p == ' ') {
            if (arg_count < argv_sz) {
                *p = '\0';
                argv[arg_count] = arg_start;
                arg_count++;
            } else {
                return -1;
            }
            arg_start = NULL;
        }
        p++;
    }

    if (arg_start && arg_count < argv_sz) {
        argv[arg_count] = arg_start;
        arg_count++;
    }

    *argc = arg_count;

    return 0;
}
